<template>
  <div class="r-table" :class="{'r-table--full':!tableOption.tableHeight}">
    <div style="padding-bottom: 10px" v-if="hasTab">
      <el-radio-group v-model="tabValue" size="mini" @change="handleChangeTabValue">
        <el-radio-button
          v-for="tab in tableOption.tab.options" :key="tab.value" :label="tab.value"
        >
          {{ tab.text }}<span class="tab__num" v-if="tab.num">({{ tab.num }})</span>
        </el-radio-button>
      </el-radio-group>
    </div>
    <div class="flex search">
      <div class="flex-con" style="margin-bottom: -10px">
        <el-form label-position="right" inline label-width="110px" size="mini" @keydown.native.enter="handleResetLoad">
          <template v-for="(col,colIndex) in showedSearchableColumns">
            <el-form-item :label="col.label" @keydown.native.enter="handleSearchKeyDown(col,$event)">
              <search-column-render :key="colIndex" class="search__item" :column="col"
                                    @change="handleSearchFieldChange(col,$event)"
                                    v-model="filterParams[col.searchProp || col.prop]"
              />
            </el-form-item>
          </template>
          <el-form-item label=" ">
            <el-button
              v-for="item in searchbarButtons"
              :key="item.name"
              :type="item.buttonType"
              size="mini"
              v-perm="item.name"
              @click="handleClickToolButton(item)"
            >
              {{ item.label }}
            </el-button>
            <el-button
              size="mini"
              style="margin-right:10px"
              v-if="searchableColumns.length && tableOption.resetBtn"
              @click="handleResetTable()"
            >重置
            </el-button
            >
          </el-form-item>

        </el-form>
      </div>
      <div v-if="searchableColumns.length > 6">
        <i class="show-toggle el-icon-arrow-up" @click="handleToggleSearchShowed(false)" v-if="searchShowed"></i>
        <i class="show-toggle el-icon-arrow-down" @click="handleToggleSearchShowed(true)" v-else></i>
      </div>
    </div>

    <loading-dialog v-model="isExporting" loading-text="正在导出，请稍等"/>

    <div class="toolbar">
      <slot name="toolBar"/>
    </div>

    <div :class="{'r-table__inner':!tableOption.tableHeight}">
      <el-table
        :height="tableOption.tableHeight?tableOption.tableHeight:'100%'"
        v-loading="listLoading"
        stripe
        size="mini"
        :default-sort="defaultSort"
        header-row-class-name="table-header-row"
        :data="tableData"
        :row-class-name="tableRowClassName"
        :row-key="tableOption.rowKey"
        @sort-change="handleSortChange"
        :tree-props="tableOption.treeProps"
        :default-expand-all="tableOption.defaultExpandAll"
        @select-all="handleSelectAll"
        ref="multipleTable"
        border
        fit
        @select="handleSelect"
        @selection-change="handleSelectionChange"
      >
        <el-table-column type="expand" v-if="tableOption.expand" fixed="left">
          <template slot-scope="scope">
            <component :is="tableOption.expand.render" v-on="expandEventHandler(tableOption.expand)" :row="scope.row"/>
          </template>
        </el-table-column>
        <el-table-column
          v-if="tableOption.showSelection"
          type="selection"
          width="40"
          align="center"
        ></el-table-column>
        <el-table-column
          type="index"
          label="序号"
          align="center"
          fixed="left"
          :index="indexMethod"
          width="50"
        >
        </el-table-column>
        <el-table-column
          v-for="(col,colIndex) in tableColumns"
          :key="colIndex"
          :prop="col.prop"
          :label="col.label"
          :sortable="col.sortable||false"
          :fixed="col.fixed || false"
          :width="col.width || '160px'"
          :min-width="col.minWidth || '160px'"
          :align="col.align || 'left'"
        >
          <template slot-scope="scope">
            <column-render :column="col" :data="scope.row" @play="handlePlay" @previewImage="handlePreviewImage"
                           @previewDetail="handlePreviewDetail"
                           @qrcode="handleShowQrcode($event,scope.row)"
                           @click="handleClickToolButton($event,scope.row)"
            />
          </template>
        </el-table-column>
        <slot name="column"/>
      </el-table>
    </div>
    <div class="pagination-container" v-if="tableOption.pagination">
      <el-pagination
        :current-page.sync="listParams.current"
        :page-sizes="[10, 20, 30, 50,100]"
        :page-size="listParams.size"
        :total="total"
        background
        layout="total, sizes, prev, pager, next, jumper"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
    <el-dialog :visible.sync="videoDialogShowed" width="840px">
      <video v-if="videoURL" ref="myVideo" width="800" height="450" :src="videoURL" controls="controls"></video>
    </el-dialog>
    <el-dialog :visible.sync="imageDialogShowed" width="840px" title="图片预览" append-to-body>
      <el-image v-for="(imgSrc,idx) in imgList" :src="imgSrc" :preview-src-list="imgList" :key="idx" fit="cover"
                class="previewImg"
      />
    </el-dialog>
    <el-dialog :visible.sync="previewDetailDialogShowed" width="840px" title="详情" append-to-body>
      <div style="line-height: 2;white-space: pre-line">
        {{ previewDetail }}
      </div>
    </el-dialog>
    <el-dialog :visible.sync="qrcodeShowed" width="340px" title="二维码" append-to-body>
      <canvas ref="canvas" style="width: 300px;height: 300px;"></canvas>
    </el-dialog>
  </div>
</template>

<script>
import ColumnType from '@/framework/constant/ColumnType'
import Selector from '@/framework/components/Selector'
import ColumnRender from '@/framework/components/table/ColumnRender'
import LoadingDialog from '@/framework/components/dialog/LoadingDialog'
import ExcelUtil from '@/framework/infra/excel/ExcelUtil'
import ColumnUtil from '@/framework/components/table/ColumnUtil'
import { mapGetters } from 'vuex'
import RouterUtil from '@/framework/infra/router/RouterUtil'
import TableButtonType from '@/framework/constant/TableButtonType'
import NameUtil from '@/framework/infra/name/NameUtil'
import DomUtil from '@/framework/infra/util/DomUtil'
import WinUtil from '@/framework/infra/util/WinUtil'
import SearchColumnRender from '@/framework/components/table/SearchColumnRender'
import CURDTable from '@/framework/components/table/CURDTable'
import ValueUtils from '@/framework/infra/value/ValueUtils'
import ObjectUtil from '@/framework/infra/object/ObjectUtil'
import TableSetting from '@/framework/components/table/TableSetting'
import * as db from '@/framework/infra/util/dbUtil'
import QRCode from 'qrcode'

export default {
  name: 'RTable',
  components: { CURDTable, SearchColumnRender, LoadingDialog, ColumnRender, Selector },
  computed: {
    ...mapGetters(['routes']),
    pageTitle() {
      return RouterUtil.getByFullPath(this.routes, this.$route.path)?.meta.title
    },
    buttons() {
      return RouterUtil.getButtonsByFullPath(this.routes, this.$route.path, this.tableOption.groupId) || []
    },
    searchCachedColumns() {
      return this.tableOption.columns.filter(el => el.searchCached)
    },
    colButtons() {
      return this.buttons.filter(el => el.type === TableButtonType.COL)
    },
    searchbarButtons() {
      const results = this.buttons.filter(el => el.type === TableButtonType.SEARCHBAR)
      // if (this.searchableColumns.length === 0 && results.length > 0) {
      //   const item = results.find(el => el.name.endsWith(':list'))
      //   if (item) {
      //     item.label = '刷新'
      //     item.icon = 'el-icon-refresh'
      //   }
      // }
      return results
    },
    searchableColumns() {
      return this.tableOption.columns.filter(el => el.searchable)
    },
    showedSearchableColumns() {
      if (this.searchShowed) {
        return this.searchableColumns
      }
      return this.searchableColumns.slice(0, 6)
    },
    tableColumns() {
      const colBtnMap = this.colButtons.reduce((r, a) => {
        r[a.col] = r[a.col] || []
        r[a.col].push(a)
        return r
      }, {})
      return this.tableOption.columns.filter(el => this.isVisible(el) && !el.hiddenInTable).map(el => {
        el.btnList = colBtnMap[el.prop] || null
        return el
      })
    },
    hasTab() {
      return this.option.tab && this.option.tab.options && this.option.tab.options.length > 0
    },
    defaultSort() {
      const sortField = this.option.columns.find(el => el.defaultSort)
      if (!sortField) {
        return {}
      }
      // const sortDesc = order === 'descending' ? true : (order === 'ascending' ? false : null)
      return {
        prop: sortField.prop,
        order: sortField.defaultSort === 'desc' ? 'descending' : 'ascending'
      }
    },
    tableOption() {
      const result = Object.assign({}, {
        columns: [],
        requestApi: null,
        pagination: true,
        exportOption: {
          remote: true
        },
        cacheSearchProps: null,
        initListParams: {},
        rowKey: 'id',
        requestApiNames: {
          list: 'list'
        },
        autoLoad: true,
        groupId: 0,
        showSelection: false,
        treeProps: {},
        defaultExpandAll: false,
        // tableHeight: window.innerHeight - 200
        tableHeight: 0
      }, this.option)

      return result
    }
  },
  data() {
    // console.error(this.option.disableDefaultSearchParam)
    return {
      videoDialogShowed: false,
      imageDialogShowed: false,
      searchShowed: false,
      imgList: [],
      videoURL: null,
      previewDetail: '',
      tabValue: '',
      previewDetailDialogShowed: false,
      qrcodeShowed: false,
      ColumnType,
      tableData: [],
      listLoading: false,
      total: 0,
      filterParams: this.getInitFilterParams(),
      isExporting: false,
      // currentRow: null,
      listParams: {
        current: 1,
        size: db.get('lovePageSize') || 20
      }
    }
  },
  watch: {
    tableColumns() {
      // console.log('doLayout')
      this.doLayout()
    },
    videoDialogShowed() {
      if (!this.videoDialogShowed && this.$refs.myVideo) {
        this.$refs.myVideo.pause()
      }
    }
  },
  methods: {
    doLayout() {
      this.$nextTick(() => {
        this.$refs.multipleTable.doLayout()
      })
    },
    generateQRCode(input) {
      const canvas = this.$refs.canvas
      QRCode.toCanvas(canvas, input, { margin: 0, width: 300, height: 300 })
    },
    handleShowQrcode(text, row) {
      this.qrcodeShowed = true
      // this.currentRow = row
      this.$nextTick(() => {
        this.generateQRCode(text)
      })
    },
    tableRowClassName({ row, rowIndex }) {
      if (this.tableOption.rowWarnFilter && this.tableOption.rowWarnFilter({ row, rowIndex })) {
        return 'warning-row'
      }
      return ''
    },
    isVisible(el) {
      if (this.$parent.isVisible) {
        return this.$parent.isVisible(el)
      }
      return true
    },
    handleChangeTabValue() {
      this.tableOption.tab?.onChange.call(this, this.tabValue)
    },
    getInitFilterParams() {
      const result = this.option.disableDefaultSearchParam ? {} : TableSetting.getDefaultSearchParam()
      const defaultLocalSearchParam = TableSetting.getDefaultLocalSearchParam() || {}
      Object.assign(result, this.option.columns.filter(el => el.searchCached).reduce((r, a) => {
        if (a.prop in defaultLocalSearchParam) {
          r[a.prop] = defaultLocalSearchParam[a.prop]
        }
        return r
      }, {}))
      this.option.columns.filter(el => el.searchOption && el.searchOption.options && el.searchOption.options.defaultValue).forEach(item => {
        result[item.prop] = item.searchOption.options.defaultValue
      })
      return result
    },
    handleToggleSearchShowed(value) {
      this.searchShowed = value
    },
    indexMethod(index) {
      return index + (this.listParams.current - 1) * this.listParams.size + 1
    },
    handleSortChange({ column, prop, order }) {
      const sortDesc = order === 'descending' ? true : (order === 'ascending' ? false : null)
      this.listParams.sortField = typeof sortDesc !== 'boolean' ? null : prop
      this.listParams.sortDesc = sortDesc // 是否倒序
      this.listParams.current = 1
      this.handleList()
    },
    handlePreviewImage(imgList) {
      this.imgList = imgList
      this.imageDialogShowed = true
    },

    handlePreviewDetail(text) {
      this.previewDetail = text
      this.previewDetailDialogShowed = true
    },
    handlePlay(v) {
      this.videoURL = v
      this.videoDialogShowed = true
    },
    expandEventHandler(expand) {
      const result = {}
      Object.keys(expand).filter(el => el.startsWith('on')).forEach(key => {
        result[NameUtil.lowerCaseFirstLetter(key.substr(2))] = expand[key]
      })
      return result
    },
    handleClickToolButton(button, arg) {
      const funcType = NameUtil.camelCase('handle', button.funcType)
      if (button.funcType === 'list') {
        this.listParams.current = 1
      }
      DomUtil.invokeClosetDomWithFuncName(this, funcType, { $table: this, arg })
    },
    handleSizeChange(val) {
      this.listParams.size = val
      db.set('lovePageSize', val)
      this.handleList()
    },
    handleSearchFieldChange(col, value) {
      if (this.searchCachedColumns.some(el => el.prop === col.prop)) {
        TableSetting.saveDefaultLocalSearchParam({
          [col.prop]: value
        })
      }
      if (!value) {
        return
      }
      if (col.type === ColumnType.DATETIME) {
        this.filterParams[col.startProp || (col.prop + 'Start')] = value[0]
        this.filterParams[col.endProp || (col.prop + 'End')] = value[1]
      }
    },
    handleCurrentChange(val) {
      this.listParams.current = val
      this.handleList()
    },
    handleSelectionChange(val) {
      console.log(val)
      this.multipleSelection = val
    },
    handleSelect(selection, current) {
      // 判断selection中是否存在current,若是存在那么就代表是被勾选上了,若是不存在代表是取消勾选了
      const isChecked = !!selection.find(item => item.id === current.id)
      // 如果当前项被取消勾选
      if (!isChecked) {
        // 那么其所有的后代也应该被取消勾选
        this.toggleCheckedChildrens(selection, current, false)
        // 那么若是所有的兄弟都被取消勾选的话,其祖先也应该被取消勾选
        this.uncheckedParents(selection, current)
      } else { // 如果当前项被勾选
        // 那么若同一组的元素都被勾选了,那么父元素将也被勾选,依次往上类推
        this.checkedParents(selection)
        // 那么其所有的后代都要被勾选
        this.toggleCheckedChildrens(selection, current, true)
      }
    },
    toggleCheckedChildrens(selection, item, isChecked) {
      var _toggleCheckedChildrens = (data) => {
        data.find(element => {
          this.$refs.multipleTable.toggleRowSelection(element, isChecked)
          if (isChecked && !selection.find(item => item.id === element.id)) {
            selection.push(element)
          } else if (!isChecked && selection.find(item => item.id === element.id)) {
            for (let i = selection.length - 1; i >= 0; i--) {
              if (selection[i].id === element.id) {
                selection.splice(i, 1)
                break
              }
            }
          }
          _toggleCheckedChildrens(element.children || [])
        })
      }
      _toggleCheckedChildrens(item.children || [])
    },
    checkedParents(selection) {
      var _checkedParents = (element) => {
        const children = element.children
        if (children && children.length) {
          const anyChildrenChecked = children.some(child => {
            return _checkedParents(child)
          })
          if (anyChildrenChecked) {
            this.$refs.multipleTable.toggleRowSelection(element, true)
            if (!selection.find(item => item.id === element.id)) {
              selection.push(element)
            }
          }
        }
        return selection.find(item => item.id === element.id)
      }
      this.tableData.forEach(element => {
        _checkedParents(element)
      })
    },
    uncheckedParents(selection, item) {
      var _uncheckedParents = (element) => {
        const children = element.children
        if (children && children.length) {
          const allChildrenUnChecked = children.every(child => {
            return _uncheckedParents(child)
          })
          if (allChildrenUnChecked) {
            this.$refs.multipleTable.toggleRowSelection(element, false)
            for (let i = selection.length - 1; i >= 0; i--) {
              if (selection[i].id === element.id) {
                selection.splice(i, 1)
                break
              }
            }
            return true
          } else {
            return false
          }
        }
        return selection.every(item => item.id !== element.id)
      }
      this.tableData.forEach(element => {
        _uncheckedParents(element)
      })
    },
    handleSelectAll(selection) {
      const isAllSelected = this.$refs.multipleTable.store.states.isAllSelected
      const _handleSelectAll = (data) => {
        data.forEach(item => {
          this.$refs.multipleTable.toggleRowSelection(item, isAllSelected)
          _handleSelectAll(item.children || [])
        })
      }
      _handleSelectAll(this.tableData)
    },
    getMultipleSelection() {
      return this.multipleSelection || []
    },
    handleResetTable() {
      this.filterParams = this.getInitFilterParams()
      location.hash = this.$route.path
      this.handleList()
    },
    async handleExport() {
      this.isExporting = true
      try {
        const exportedColumns = this.tableOption.columns.filter(el =>
          el.type !== ColumnType.RICH_EDITOR &&
          el.type !== ColumnType.PASSWORD &&
          el.type !== ColumnType.IMAGE &&
          el.exported !== false &&
          el.hiddenInTable !== true
        )
        if (!this.tableOption.exportOption?.remote) {
          await this.clientExport(exportedColumns)
        } else {
          await this.remoteExport(exportedColumns)
        }
      } finally {
        this.isExporting = false
      }
    },
    async remoteExport(exportedColumns) {
      // 后端导出
      const columns = []
      for (const col of exportedColumns) {
        const item = {
          label: col.label,
          prop: col.displayProp || col.prop
        }
        const options = col.displayProp ? null : await ColumnUtil.parseOptionToMap(col)
        if (options) {
          item.options = options
        }
        columns.push(item)
      }
      const exportOption = {
        title: this.pageTitle,
        columns
      }
      this.tableOption.requestApi.export({
        filter: this.getFinalListParams(),
        exportOption
      })
      // const url = await this.tableOption.requestApi.export({
      //   filter: this.getFinalListParams(),
      //   exportOption
      // })
      // WinUtil.openWin(url)
    },
    getFinalListParams() {
      return {
        ...this.listParams,
        ...this.filterParams,
        ...ValueUtils.getValue(this.tableOption.initListParams)
      }
    },
    // 前端导出
    async clientExport(exportedColumns) {
      let data
      if (!this.tableOption.pagination) {
        data = await this.tableOption.requestApi[this.tableOption.requestApiNames.list]({
          ...this.getFinalListParams(),
          pageNo: 1,
          pageSize: 10000
        })
      } else {
        const result = await this.tableOption.requestApi[this.tableOption.requestApiNames.list]({
          ...this.getFinalListParams(),
          pageNo: 1,
          pageSize: 10000
        })
        if (result) {
          data = result.data || []
        }
      }

      const header = exportedColumns.map(el => el.label)
      const exportedData = []
      if (data) {
        for (const row of data) {
          const exportedItem = []
          for (const col of exportedColumns) {
            const value = ObjectUtil.getPropValue(row, col.displayProp || col.prop)
            exportedItem.push(await ColumnUtil.getText(col, value))
          }
          exportedData.push(exportedItem)
        }
      }
      ExcelUtil.exportTable({
        header,
        data: exportedData,
        filename: this.pageTitle
      })
    },
    handleSearchKeyDown(col, $evt) {
      if (col.searchOption?.options.type === ColumnType.TEXTAREA) {
        $evt.stopPropagation()
      }
    },
    handleResetLoad() {
      this.listParams.current = 1
      this.handleList()
    },
    async handleList() {
      this.listLoading = true
      try {
        const result = await this.tableOption.requestApi[this.tableOption.requestApiNames.list](this.getFinalListParams())
        if (this.tableOption.pagination) {
          console.log(result, 'handleList')
          const {
            data,
            records,
            total
          } = result
          this.tableData = data || records || []
          this.total = parseInt(total)
        } else {
          this.tableData = result || []
        }
        this.$emit('onLoadData', this.tableData)
        this.$nextTick(() => {
          // doLayout：对 Table 进行重新布局。当 Table 或其祖先元素由隐藏切换为显示时
          // 可能需要调用此方法
          this.$refs.multipleTable.doLayout()
        })
      } finally {
        this.listLoading = false
      }
    }
  },
  created() {
    if (this.option.columns) {
      const sortField = this.option.columns.find(el => el.defaultSort)
      if (sortField) {
        /**
         * sortField: gmtCreate
         sortDesc: false
         */
        this.listParams.sortField = sortField.prop
        this.listParams.sortDesc = sortField.defaultSort === 'desc'
      }
    }
    if (this.tableOption.requestApi && this.option.autoLoad) {
      this.handleList()
    }
    this.tabValue = this.tableOption?.tab?.defaultValue
  },
  props: {
    option: {
      type: Object,
      default() {
        return {}
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.pagination-container {
  text-align: right;
}

.r-table {

  display: flex;
  flex-direction: column;

  &__inner {
    flex: 1;
    height: 0;
  }

  &--full {
    height: calc(100vh - 110px);
  }
}

.previewImg {
  width: 140px;
  height: 140px;
  margin-left: 10px;

  &:first-child {
    margin-left: 0;
  }
}

.search {
  background-color: #f7f8fa;
  align-items: flex-start;
  border-radius: 10px;
  padding: 20px 10px;
  box-sizing: border-box;
  //margin-bottom: -20px;

  &__item {
    width: 180px;

    &--span {
      width: 100%;
    }
  }

  ::v-deep .el-form-item__label {
    font-size: 12px;
  }

  ::v-deep .el-form-item--mini.el-form-item {
    margin-bottom: 10px;
  }


}

::v-deep .el-table--mini .el-table__cell {
  padding: 2px 0;
}

::v-deep .pagination-container {
  padding-top: 15px;
}

.toolbar {
  padding-top: 10px;
  //padding-bottom: 10px;
}

.show-toggle {
  margin-right: 20px;
  font-size: 24px;
  font-weight: bold;
}

.tab__num {
  font-weight: bold;
  color: #E33737;
}

::v-deep .el-radio-button__orig-radio:checked + .el-radio-button__inner {
  .tab__num {
    color: #fff;
  }
}
</style>
